package com.veeder.datacollection.rs232;

import java.io.IOException;

import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.veeder.datacollection.config.ConnParamConfig;
import com.veeder.datacollection.config.ConnectionParam;
import com.veeder.datacollection.exception.ConfigurationException;
import com.veeder.datacollection.exception.TimeoutException;
import com.veeder.datacollection.rs232.comm.Connection;
import com.veeder.datacollection.rs232.comm.ConnectionFactory;
import com.veeder.datacollection.util.GlobalConstants;

public class RS232CommandExecutor implements Command
{

   public static final Charset CHARSET = Charset.forName("utf-8");
   public static final String ACK = new String(new byte[] { '\u0006' });

   private Connection connection;
   protected final Logger logger;
   private static ConnectionParam connectionParam;
   private static Object connectionLock = new Object();
   private static RS232CommandExecutor instance = new RS232CommandExecutor();

   public static RS232CommandExecutor getInstance()
   {
      return instance;
   }

   public static ConnectionParam getConnectionParam()
   {
      return connectionParam;
   }

   private RS232CommandExecutor()
   {
      this.logger = Logger.getLogger(this.getClass().getName());
      logger.setUseParentHandlers(true);
      try
      {
         connectionParam = ConnParamConfig.loadParams(GlobalConstants.ConfigFileLocation
                                                            + "Collector.ini",
                                                      "ATGConfig");
         connection = ConnectionFactory.getConnection(connectionParam, 5, 3);
      }
      catch (ConfigurationException e)
      {
         logger.log(Level.SEVERE,
                    "read from configuration file failure" + e.toString());
      }
   }

   public String executeCommand(String command, boolean closeConnectionAfter)
   {
      return executeCommand(5, command, closeConnectionAfter, false, 2);
   }

   public String executeCommand(String command,
                                boolean closeConnectionAfter,
                                boolean isCompleteCmd)
   {
      return executeCommand(5, command, closeConnectionAfter, isCompleteCmd, 2);
   }

   public void closeConnection()
   {
      synchronized (connectionLock)
      {
         connection.closeConnection();
      }
   }
   

  
   public String executeCommand(int readRetryTimes,
                                 String command,
                                 boolean closeConnectionAfter,
                                 boolean isCompleteCmd,
                                 int retryTimes)
   {
      String str = null;
      if (command == null || command.length() <= 0)
      {
         logger.log(Level.WARNING, "invalid command: " + command);
      }
      else
      {
         synchronized (connectionLock)
         {
            boolean isSuccess = false;
            int retryCount = 0;

            while (!isSuccess && retryCount < retryTimes)
            {
               // check the connection;
               // reset the connection if it's not valid.
               if (!connection.isValid())
               {
                  try
                  {
                     connection.openConnection();

                  }
                  catch (Exception e)
                  {
                     logger.log(Level.SEVERE,
                                "open connetion error "
                                      + e.toString()
                                      + " ,command: "
                                      + command);
                     connection.closeConnection();
                  }
               }

               if (connection.isValid())
               {
                  try
                  {
                     writeCompleteCmd(command, isCompleteCmd);
                     str = readCompleteRsp(readRetryTimes);
                     if (str != null && str.length() > 0)
                     {
                        isSuccess = true;
                     }
                  }
                  catch (Exception e)
                  {
                     logger.log(Level.WARNING,
                                "read or write to connection error "
                                      + e.toString()
                                      + " ,command: "
                                      + command);
                     connection.closeConnection();
                  }
               }
               
               
               retryCount++;
            }

            // close connection after reading.
            if (closeConnectionAfter)
            {
               connection.closeConnection();
            }
         }
      };

      return str;
   }

   /**
    * Write and send out a complete command.
    * 
    * @param str
    * @throws IOException
    */
   public void writeCompleteCmd(String str, boolean isCompleteCmd) throws IOException
   {
      logger.log(Level.INFO, "write frame: " + str);
      if (str != null && str.length() != 0)
      {
         synchronized(connectionLock){
         if (isCompleteCmd)
         {
            connection.write(str.getBytes(CHARSET));
         }
         else
         {
            connection.write(Command.SOH.getBytes());
            connection.write(str.getBytes(CHARSET));
            // this is very important,
            // the loss of this part probably prevents some of commands
            // returning its results.
            connection.write(Command.EOF.getBytes());
         }

         connection.flush();
         
         }
      }
   }

   /**
    * Read a complete response.
    * 
    * @param retryTimes
    * @return
    * @throws IOException
    * @throws TimeoutException
    */
   private String readCompleteRsp(int maxRetries) throws IOException
   {
      String response=null;
      StringBuilder recvBuffer = new StringBuilder();
      byte[] btBuf = new byte[1024];
      boolean complete = false;
      int retryCount = 0;
      int start=-1;
      do
      {
         try
         {
            int size = connection.read(btBuf);

            if (size > 0)
            {
               String str = new String(btBuf, 0, size, CHARSET);
               recvBuffer.append(str);
               if(str.equals(ACK)){
                  start=0;
                  complete=true;
               }
               else{
               
               start = recvBuffer.indexOf(Command.SOH);
               if (start >= 0)
               {
                  int end = recvBuffer.lastIndexOf(Command.ETX);
                  complete = (end > start);
               }
            }

            }
         }
         catch (TimeoutException e)
         {
            retryCount++;
         }
      }
      while (!complete && retryCount < maxRetries);

   
      logger.log(Level.INFO, "read frame: " + recvBuffer.toString());
      if(complete){
         response = recvBuffer.toString().substring(start);
      }
      return response;
   }

}
